/*
    ActionStrip.js
    Copyright (C) 2010-2011, Donald W. Griffin
    All Rights Reserved
*/
Ext.namespace("Wdg");

Wdg.ActionStrip = Ext.extendX(Ext.util.Observable, function (base) {
// private
    function _getKey (act) {
        return act.id;
    }

    function _mkdiv (body, width, cls) {
        var w = (typeof(width) === "number") ? (" style='width:"+width+"px;'") : '';
        var c = cls ? (" class='" + cls + "'") : '';
        var a = [ "<div", c, w, ">" ];
        if (typeof(body) === "string")
            a.push(body);
        else
            a = a.concat(body);
        a.push("</div>");
        return a.join('');
    }

// public
return {
    actionTpl:
        ['<div class="{baseCls}-ct {cls}" style="{vis}">',
         '<tpl if="link"><a href="{link}" class="{baseCls}-link {linkCls}"></tpl>',
         '<div id="{id}" wdgAction="{index}" class="{baseCls}-item',
         '<tpl if="!text"> {baseCls}-item-notext</tpl> {icon}" style="{style}" ',
         'wdgStripId="{stripId}" <tpl if="qtip"> qtip="{qtip}"</tpl>>',
         '<tpl if="text"><span class="{baseCls}-text {textCls}">{text}</span></tpl>',
         '</div><tpl if="link"></a></tpl></div>'].join(''),

    baseCls: "wdg-actionstrip",

    bubbleEvents: ["beforeaction", "action"],

    itemGap: 2,
    itemWidth: 16,

    linkCls: "",
    textCls: "",

    constructor: function(config) {
        Ext.apply(this, config);

        this.actionTpl = new Ext.XTemplate(this.actionTpl);
        this.id = this.id || Ext.id();
        this.itemRegex = new RegExp("(?:^|\\s)"+this.baseCls+"-item(?=$|\\s)");

        this.addEvents(
            "beforeaction",
            "action"
        );

        this.enableBubble(this.bubbleEvents);

        base.constructor.call(this); // handle listeners config (must addEvents first)

        var actions = this.actions;
        this.actions = new Ext.util.MixedCollection(false, _getKey);
        this.prepare(actions);
    },

    dispatch: function (ed) {
        if (false !== this.fireEvent("beforeaction", ed)) {
            var a = ed.action, fn = a.fn || a.handler;
            var scope = a.scope || this.scope || this.owner.scope;

            fn.call(scope, ed);
            this.fireEvent("action", ed);
        }
    },

    each: function (fn, scope) {
        this.actions.each(fn, scope || this);
    },

    matchEvent: function (e) {
        var owner = this.owner;
        if (this.eventsSuspended || owner.component.eventsSuspended)
            return null;
        var t = e.target, cn = t.className;
        if (!this.itemRegex.test(cn))
            return null;

        var id = t.getAttribute("wdgStripId");
        if (id !== this.id)
            return null;

        var k = t.getAttribute("wdgAction"), a = this.actions.itemAt(k);
        var ed = a.getEventData(e);
        ed.actions = this;

        return ed;
    },

    prepare: function (actions) {
        var index = 0, width = this.itemGap * (actions.length + 1);
        var A = this.defaultType || "action";

        Ext.each(actions, function (act) {
            var a = act;
            if (!a.isAction)
            {
                var t = act.xtype || A;
                var T = Wdg.ActionStrip.types[t];
                a = new T(act);
            }

            a.prepare(this, index++);

            width += (a.width = a.width || this.itemWidth);

            this.actions.add(a);
        }, this);

        this.width = this.width || width;
    },

    render: function (rd, predFn, predScope) {
        var include, parts = {}, widths = {};
        rd.actions = this;
        rd.count = this.actions.getCount();

        if (predFn) {
            var scope = predScope || this;
            include = function (act) {
                return act.include.call(act.scope || act, rd) && predFn.call(scope, act, rd);
            };
        } else {
            include = function (act) {
                return act.include.call(act.scope || act, rd);
            };
        }

        this.each(function (act) {
            rd.action = act;
            if (include(act)) {
                var a = rd.splitAlign ? act.align : "left";
                rd.first = !(a in parts);
                var s = act.render(rd);
                if (rd.first)
                    parts[a] = [s];
                else
                    parts[a].push(s);

                widths[a] = ((a in widths) ? (widths[a] + this.itemGap) : 0) + act.width;
            }
        });

        if (!rd.splitAlign)
            return _mkdiv(parts.left, widths.left, rd.cls || this.baseCls);

        var html = [];
        if (parts.right)
            html.push(_mkdiv(parts.right, widths.right, this.baseCls+"-right"));
        if (parts.left)
            html.push(_mkdiv(parts.left, widths.left, this.baseCls+"-left"));
        if (parts.center)
            html.push(_mkdiv(parts.center, widths.center, this.baseCls+"-center"));

        var s = html.join('');
        return s;
    },

    update: function (rd) {
        rd.actions = this;
        rd.count = this.actions.getCount();

        this.each(function (act) {
            act.update(rd);
        });
    }
};

}); // Wdg.ActionStrip

Wdg.ActionStrip.types = {};

Wdg.ActionStrip.reg = function (A) {
    var xtype = A.prototype.xtype;
    Wdg.ActionStrip.types[xtype] = A;
};

//-----------------------------------------------------------------

Wdg.ActionStrip.Action = Ext.extendX(Object, function () {
// private
    var _props = ["cls", "icon", "link", "qtip", "style", "text" ];

    function _blank () {
        return "";
    }

    function _Chainer () { }

    function _chain (obj) {
        _Chainer.prototype = obj;
        return new _Chainer();
    }

    function _prepValue (name) {
        var v = this[name], t = typeof(v);

        if (t === "string") {
            var tpl = new Ext.XTemplate(v);
            this[name] = function (rd) {
                var p = _chain(rd.values);
                p.$ = rd;
                return tpl.apply(p);
            };
        }
    }

    function _wrapValue (b) {
        return function () { return b; };
    }

// public
return {
    align: "left",
    isAction: true,
    cls:   _blank,
    icon:  _blank,
    link:  _blank,
    qtip:  _blank,
    style: _blank,
    text:  _blank,
    xtype: "action",

    hide: function (rd) {
        return false;
    },

    include: function (rd) {
        return true;
    },

    constructor: function (cfg) {
        Ext.apply(this, cfg);
        var qtip = cfg.hint || cfg.tip;
        if (qtip)
            this.qtip = qtip;

        if (typeof(this.hide) === "boolean")
            this.hide = _wrapValue(this.hide);

        this.id = this.id || Ext.id();
        if (typeof(this.tpl) === "string") {
            this.actionTpl = new Ext.XTemplate(this.tpl);
            delete this.tpl;
        }

        Ext.each(_props, _prepValue, this);
    },

    getEventData: function (e) {
        var ptxy = e.getXY(), exy = Ext.fly(e.target).getXY();
        var ed = {
            action: this,
            event: e,
            selectItem: this.lookupProp("actionSelectsItem", true),
            x: ptxy[0] - exy[0],
            y: ptxy[1] - exy[1]
        };
        return ed;
    },

    getHideMode: function () {
        return this.lookupProp("hideMode", "visibility");
    },

    getTplData: function (rd) {
        rd.action = this;
        var strip = this.strip, scope = this.scope || this;

        var td = {
            baseCls: this.lookupProp("baseCls"),
            cls:  this.cls.call(scope, rd),
            count: rd.count,
            icon: this.icon.call(scope, rd),
            index: this.index,
            link: this.link.call(scope, rd),
            linkCls: this.lookupProp("linkCls"),
            qtip: this.qtip.call(scope, rd),
            stripId: strip.id,
            style: (rd.first ? "" : ("margin-left:" + strip.itemGap + "px;")) +
                this.style.call(scope, rd),
            text: this.text.call(scope, rd),
            textCls: this.lookupProp("textCls"),
            vis: this.vis(rd)
        };
        td.id = rd.baseId + (rd.id || td.icon);

        return td;
    },

    lookupProp: function (name, defVal) {
        if (name in this)
            return this[name];
        var p = this.strip;
        if (p) {
            if (name in p)
                return p[name];
            p = p.owner;
            if (p && (name in p))
                return p[name];
        }
        return defVal;
    },

    prepare: function (strip, index) {
        this.index = index;
        this.strip = strip;
    },

    render: function (rd) {
        var td = this.getTplData(rd);
        var tpl = this.lookupProp("actionTpl");
        return tpl.apply(td);
    },

    update: function (rd) {
        var td = this.getTplData(rd);
        var f = Ext.fly(td.id);
        if (!f)
            return;

        var el = f.dom;
        f.qtip = td.qtip;

        var hm = this.getHideMode().toUpperCase(); // VISIBILITY or DISPLAY
        Ext.fly(el).setVisibilityMode(Ext.Element[hm]).setVisible(!td.vis);
    },

    vis: function (rd) {
        var hm;
        if (this.hide.call(this.scope || this, rd)) {
            hm = this.getHideMode();
            hm += (hm === "display") ? ":none;" : ":hidden;";
        }
        return hm || "";
    }
};

}); // Wdg.ActionStrip.Action

Wdg.ActionStrip.reg(Wdg.ActionStrip.Action);

//-----------------------------------------------------------------

/**
*/
Wdg.ActionStripPlugin = Ext.extendX(Object, function () {

    var _fwdProps = [
        "actions",
        "defaultType",
        "itemGap",
        "itemWidth"
    ];

    return {
        constructor: function(config) {
            Wdg.ActionStripPlugin.superclass.constructor.call(this);
            Ext.apply(this, config);

            this.id = this.id || Ext.id();

            var acfg = this.getActionCfg();
            this.actions = new Wdg.ActionStrip(acfg);
        },

        getActionCfg: function () {
            var cfg = {
                owner: this
            };

            Ext.each(_fwdProps, function (p) {
                var v = this[p]
                if (typeof(v) !== "undefined")
                    cfg[p] = v;
            }, this);

            return cfg;
        },

        init: function (cmp) {
            this.component = cmp;
        },

        destroy: Ext.emptyFn // required in some cases
    };
});

/**
This class is a GridPanel plugin that adds actions to the grid. There are multipe
ways to add GridActions to a grid.

The most common pattern for adding actions to a grid is called "row actions". This
is done by adding the GridActions to the column model of the grid (as well as to
the plugins). Actions are placed in this column in the order they appear in the
actions array.

Actions can also be added to cells (so called "cell actions"). This is done by
adding either a "cell" string or a "cells" array of strings to the action config.
These strings are the column id's of the column(s) to which the action will be
added.

Lastly, actions can be added to the headers of a grid's GroupingView. These are
called "group actions". The simplest way to accomplish this is set the group config
option for the action to true. This will include the action regardless of the
column by which the grid is being grouped. Alternatively, group can be a string
with is the column id to which the action applies. Only when the grid is grouped
by that column will the action be added. As with cell/cells, an action can use the
groups config property to specify that the action applies when grouped by many
different columns.

In a nutshell:

    !cells && !groups   :   row action
    cells && !groups    :   cell action
    !cells && groups    :   group action
    cells && groups     :   cell & group action

Remember, any cell action can also be a row action if the id of the GridActions
column is specified a cell to which the action is related.
*/
Wdg.GridActions = Ext.extendX(Wdg.ActionStripPlugin, function (base) {
    return {
        actionSelectsItem: true,
        defaultType: "gridaction",

        // Column stuff:
        dataIndex: "",
        fixed: true,
        header: "",
        isColumn: true,
        menuDisabled: true,
        sortable: false,
        //width: 200,//TODO

        constructor: function(config) {
            base.constructor.call(this, config);

            if (!this.width) {
                var w = this.actions.itemGap, rd = { meta: { id: this.id } };
                this.actions.each(function (a) {
                    rd.action = a;
                    if (a.include.call(a.scope || a, rd))
                        w += this.actions.itemGap + a.width;
                }, this);
                this.width = w;
            }

            if (!this.renderer)
                this.renderer = this.doRender.createDelegate(this);
        },

        doRender: function (value, metaData, record, rowIndex, colIndex, store) {
            var css = metaData.css;
            metaData.css = (css ? (css + ' ') : "") + this.actions.baseCls + "-cell";
            return this.actions.render({
                baseId: this.component.id + "_row" + rowIndex + '_',
                column: colIndex,
                meta: metaData,
                record: record,
                row: rowIndex,
                store: store,
                value: value,
                values: record.data
            });
        },

        doRenderGroup: function (values, after) {
            var grid = this.component, view = grid.getView(), gfield = view.getGroupField();
            var rd = {
                baseId: grid.id + "_group" + values.startRow + '_',
                cls: after ? null : (this.actions.baseCls+"-pre"),
                preActions: !after,
                splitAlign: after,
                groupCol: gfield,
                groupId: values.groupId,
                value: values.gvalue,
                records: values.rs,
                startRow: values.startRow,
                text: values.text
            };
            return this.actions.render(rd);
        },

        init: function (grid) {
            base.init.call(this, grid);

            this._processEvent = grid.processEvent;
            grid.processEvent = this.onEvent.createDelegate(this);

            if (this.actions.cells) {
                grid.on("reconfigure", this.initCells, this);
                this.initCells();
            }

            var view = grid.getView();

            if (view.groupTextTpl) {
                if (view.interceptMouse) { // if (ExtVer < 3.3)
                    view.interceptMouse =
                        view.interceptMouse.createInterceptor(this.onFilterEvent, this);
                }

                view.groupTextTpl =
                    ['<div class="',this.actions.baseCls,'-grid-group-pre">',
                    '{[this.renderGroupActions(values,0)]}</div>',
                    '<div class="',this.actions.baseCls,'-grid-group-text">',
                        view.groupTextTpl,'</div>',
                    '{[this.renderGroupActions(values,1)]}',
                    '<div style="clear:both;"></div>'].join('');

                view.initTemplates = view.initTemplates.createSequence(function () {
                    view.startGroup.renderGroupActions = this.doRenderGroup.createDelegate(this);
                }, this);
            }
        },

        initCell: function (col) {
            var rfn = col.renderer, self = this;
            if (!col.groupRenderer)
                col.groupRenderer = rfn;
            col.renderer = function (value, metaData, record, rowIndex, colIndex, store) {
                var rd = {
                    baseId: self.component.id + "_row" + rowIndex + '_' + col.id + '_',
                    cls: self.actions.baseCls + "-pre",
                    preActions: true,
                    splitAlign: false,
                    cellCol: col.id,
                    column: colIndex,
                    meta: metaData,
                    record: record,
                    row: rowIndex,
                    store: store,
                    value: value,
                    values: record.data
                };
                var html = [ self.actions.render(rd) ];

                html.push("<div style='float:left; margin: 0 4px;'>");
                html.push(rfn ? rfn.apply(this, arguments) : value);
                html.push("</div>");

                delete rd.cls;
                rd.preActions = false;
                rd.splitAlign = true;
                html.push(self.actions.render(rd));

                return html.join('');
            };
        },

        initCells: function () {
            var cm = this.component.getColumnModel();
            Ext.iterate(this.actions.cells, function (cell) {
                if (cell == this.id)
                    return;
                var col = cm.getColumnById(cell);
                this.initCell(col);
            }, this);
        },

        onFilterEvent: function (e) {
            var ed = this.actions.matchEvent(e);
            return ed == null;
        },

        onEvent: function (name, e) {
            if (name === "click") {
                var ed = this.actions.matchEvent(e);
                if (ed) {
                    var row = e.getTarget(".x-grid3-row");
                    if (row) {
                        var rec = this.component.store.getAt(row.rowIndex);
                        Ext.apply(ed, {
                            item: rec.data,
                            record: rec
                        });
                    } else {
                        var view = this.component.getView(), grp = view.findGroup(e.target);
                        var gid = grp.id, recs = [];
                        this.component.store.each(function (rec) {
                            if (rec._groupId == gid)
                                recs.push(rec);
                        });
                        ed.groupCol = view.getGroupField();
                        ed.records = recs;
                    }

                    this.actions.dispatch(ed);
                    if (!ed.selectItem)
                        return;
                }
            }

            this._processEvent.apply(this.component, arguments); // pass on
        }
    };
}); // Wdg.GridActions

//-----------------------------------------------------------------

Wdg.GridActions.Action = Ext.extendX(Wdg.ActionStrip.Action, function (base) {
// private

    function makeMap (name) {
        var map, plural = name+'s';
        if (name in this) {
            map = {};
            map[this[name]] = true;
            delete this[name];
        } else if (plural in this) {
            map = {};
            Ext.each(this[plural], function (key) {
                map[key] = true;
            });
        }

        if (map)
            this[plural] = map;
    }

// public
return {
    always: true,
    xtype: "gridaction",

    constructor: function (cfg) {
        base.constructor.call(this, cfg);

        makeMap.call(this, "cell");
        if (typeof(this.group) !== "boolean")
            makeMap.call(this, "group");
    },

    appliesTo: function (rd) {
        var b;

        if (!rd.cellCol && !rd.groupCol) { // if (row actions)
            b = this.cells ? (rd.meta && rd.meta.id in this.cells)
                        : !(this.group || this.groups);
        } else {
            if (rd.cellCol) // if (cell actions)
                b = this.cells && (rd.cellCol in this.cells);
            else { // else (group actions)
                b = this.group || (this.groups && (rd.groupCol in this.groups));
                if (b && !this.always) {
                    var rd2 = { action: this, meta: { id: this.strip.owner.id } };
                    var scope = this.scope || this;
                    Ext.each(rd.records, function (rec) {
                        rd2.record = rec;
                        rd2.values = rec.data;
                        b = this.include.call(scope, rd2) && !this.hide.call(scope, rd2);
                        return b;
                    }, this);
                }
            }

            b = b && (rd.preActions == (this.align === "pre"));
        }

        return b;
    },

    include: function (rd) {
        return rd.action.appliesTo(rd); // rd.action may not === this
    },

    prepare: function (strip, index) {
        base.prepare.call(this, strip, index);

        if (this.cells) {
            if (!strip.cells)
                strip.cells = {};
            Ext.iterate(this.cells, function (name) {
                strip.cells[name] = true;
            });
        }
    }
};

});

Wdg.ActionStrip.reg(Wdg.GridActions.Action);

//-----------------------------------------------------------------------------

Wdg.FormActions = Ext.extendX(Wdg.ActionStripPlugin, function (base) {
    return {
        constructor: function (config) {
            base.constructor.call(this, config);

            //
        },

        init: function (form) {
            base.init.call(this, form);

            form.on({
                afterlayout: this.onAfterLayout,
                afterrender: this.onAfterRender,
                scope: this
            });
        },

        eachItem: function (fn) {
            this.component.items.each(fn, this);
        },

        getValues: function () {
            var ret = {};

            this.eachItem(function (item) {
                if (!item.getValue)
                    return;

                var v = item.getValue();

                if ("hiddenName" in item) {
                    ret[item.hiddenName] = v;
                    if (item.getRawValue)
                        v = item.getRawValue();
                }

                ret[item.name || item.id] = v;
            });

            return ret;
        },

        onAfterRender: function () {
            this.component.mon(this.component.el, {
                click: this.onClick,
                scope: this
            });
        },

        onChange: function (item) {
            var values = this.getValues();

            this.eachItem(function (item) {
                this.actions.update({
                    baseId: item.id + '_',
                    error: item.activeError,
                    item: item,
                    value: item.getValue(),
                    values: values
                });
            })
        },

        onClick: function (e) {
            if (e.type === "click") {
                var label = e.getTarget("label");
                var ed = this.actions.matchEvent(e);
                if (ed) {
                    var itemId = label._itemId;
                    var item = Ext.getCmp(itemId);

                    Ext.apply(ed, {
                        error: item.activeError,
                        form: this.component,
                        item: item,
                        value: item.getValue(),
                        values: this.getValues()
                    });

                    this.actions.dispatch(ed);
                    e.stopEvent();
                }
            }
        },

        onAfterLayout: function () {
            var values = this.getValues();

            this.eachItem(function (item) {
                if (item._formAction || !item.label)
                    return;

                this.component.mon(item, {
                    change: this.onChange,
                    scope: this
                });
                var html = this.actions.render({
                    baseId: item.id + '_',
                    error: item.activeError,
                    item: item,
                    value: item.getValue && item.getValue(),
                    values: values
                });

                var s = item.label.dom.innerHTML;
                s = "<div style='float:left;'>" + s + "</div>";

                item.label.dom.innerHTML = s + html + "<div style='clear:both;'></div>";
                item._formAction = true;
                item.label.dom._itemId = item.getId();
            });
        }
    };
});
